home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / Auth / Container / LDAP.php < prev    next >
PHP Script  |  2004-03-24  |  18KB  |  473 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Jan Wagner <wagner@netsols.de>                              |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: LDAP.php,v 1.14 2003/06/02 16:55:10 mj Exp $
  20. //
  21.  
  22. require_once "Auth/Container.php";
  23. require_once "PEAR.php";
  24.  
  25. /**
  26.  * Storage driver for fetching login data from LDAP
  27.  *
  28.  * This class is heavily based on the DB and File containers. By default it
  29.  * connects to localhost:389 and searches for uid=$username with the scope
  30.  * "sub". If no search base is specified, it will try to determine it via
  31.  * the namingContexts attribute. It takes its parameters in a hash, connects
  32.  * to the ldap server, binds anonymously, searches for the user, and tries
  33.  * to bind as the user with the supplied password. When a group was set, it
  34.  * will look for group membership of the authenticated user. If all goes
  35.  * well the authentication was successful.
  36.  *
  37.  * Parameters:
  38.  *
  39.  * host:        localhost (default), ldap.netsols.de or 127.0.0.1
  40.  * port:        389 (default) or 636 or whereever your server runs
  41.  * url:         ldap://localhost:389/
  42.  *              useful for ldaps://, works only with openldap2 ?
  43.  *              it will be preferred over host and port
  44.  * binddn:      If set, searching for user will be done after binding
  45.  *              as this user, if not set the bind will be anonymous.
  46.  *              This is reported to make the container work with MS
  47.  *              Active Directory, but should work with any server that
  48.  *              is configured this way.
  49.  *              This has to be a complete dn for now (basedn and
  50.  *              userdn will not be appended).
  51.  * bindpw:      The password to use for binding with binddn
  52.  * scope:       one, sub (default), or base
  53.  * basedn:      the base dn of your server
  54.  * userdn:      gets prepended to basedn when searching for user
  55.  * userattr:    the user attribute to search for (default: uid)
  56.  * useroc:      objectclass of user (for the search filter)
  57.  *              (default: posixAccount)
  58.  * groupdn:     gets prepended to basedn when searching for group
  59.  * groupattr  : the group attribute to search for (default: cn)
  60.  * groupoc    : objectclass of group (for the search filter)
  61.  *              (default: groupOfUniqueNames)
  62.  * memberattr : the attribute of the group object where the user dn
  63.  *              may be found (default: uniqueMember)
  64.  * memberisdn:  whether the memberattr is the dn of the user (default)
  65.  *              or the value of userattr (usually uid)
  66.  * group:       the name of group to search for
  67.  * debug:       Enable/Disable debugging output (default: false)
  68.  *
  69.  * To use this storage container, you have to use the following syntax:
  70.  *
  71.  * <?php
  72.  * ...
  73.  *
  74.  * $a = new Auth("LDAP", array(
  75.  *       'host' => 'localhost',
  76.  *       'port' => '389',
  77.  *       'basedn' => 'o=netsols,c=de',
  78.  *       'userattr' => 'uid'
  79.  *       'binddn' => 'cn=admin,o=netsols,c=de',
  80.  *       'bindpw' => 'password'));
  81.  *
  82.  * $a2 = new Auth('LDAP', array(
  83.  *       'url' => 'ldaps://ldap.netsols.de',
  84.  *       'basedn' => 'o=netsols,c=de',
  85.  *       'scope' => 'one',
  86.  *       'userdn' => 'ou=People',
  87.  *       'groupdn' => 'ou=Groups',
  88.  *       'groupoc' => 'posixGroup',
  89.  *       'memberattr' => 'memberUid',
  90.  *       'memberisdn' => false,
  91.  *       'group' => 'admin'
  92.  *       ));
  93.  *
  94.  * $a3 = new Auth('LDAP', array(
  95.  *         'host' => 'ad.netsols.de',
  96.  *         'basedn' => 'dc=netsols,dc=de',
  97.  *         'userdn' => 'ou=Users',
  98.  *         'binddn' => 'cn=Jan Wagner,ou=Users,dc=netsols,dc=de',
  99.  *         'bindpw' => '*******',
  100.  *         'userattr' => 'samAccountName',
  101.  *         'useroc' => 'user',
  102.  *          'debug' => true
  103.  *         ));             
  104.  *
  105.  * The parameter values have to correspond
  106.  * to the ones for your LDAP server of course.
  107.  *
  108.  * When talking to a Microsoft ActiveDirectory server you have to
  109.  * use 'samaccountname' as the 'userattr' and follow special rules
  110.  * to translate the ActiveDirectory directory names into 'basedn'.
  111.  * The 'basedn' for the default 'Users' folder on an ActiveDirectory
  112.  * server for the ActiveDirectory Domain (which is not related to
  113.  * its DNS name) "win2000.example.org" would be:
  114.  * "CN=Users, DC=win2000, DC=example, DC=org'
  115.  * where every component of the domain name becomes a DC attribute
  116.  * of its own. If you want to use a custom users folder you have to
  117.  * replace "CN=Users" with a sequence of "OU" attributes that specify
  118.  * the path to your custom folder in reverse order.
  119.  * So the ActiveDirectory folder
  120.  *   "win2000.example.org\Custom\Accounts"
  121.  * would become
  122.  *   "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
  123.  *
  124.  * It seems that binding anonymously to an Active Directory
  125.  * is not allowed, so you have to set binddn and bindpw for
  126.  * user searching,
  127.  *
  128.  * Example a3 shows a tested example for connenction to Windows 2000
  129.  * Active Directory
  130.  *
  131.  * @author   Jan Wagner <wagner@netsols.de>
  132.  * @package  Auth
  133.  * @version  $Revision: 1.14 $
  134.  */
  135. class Auth_Container_LDAP extends Auth_Container
  136. {
  137.     /**
  138.      * Options for the class
  139.      * @var array
  140.      */
  141.     var $options = array();
  142.  
  143.     /**
  144.      * Connection ID of LDAP Link
  145.      * @var string
  146.      */
  147.     var $conn_id = false;
  148.  
  149.     /**
  150.      * LDAP search function to use
  151.      * @var string
  152.      */
  153.     var $ldap_search_func;
  154.  
  155.     /**
  156.      * Constructor of the container class
  157.      *
  158.      * @param  $params, associative hash with host,port,basedn and userattr key
  159.      * @return object Returns an error object if something went wrong
  160.      */
  161.     function Auth_Container_LDAP($params)
  162.     {
  163.         $this->_setDefaults();
  164.  
  165.         if (is_array($params)) {
  166.             $this->_parseOptions($params);
  167.         }
  168.     }
  169.  
  170.     // }}}
  171.     // {{{ _connect()
  172.  
  173.     /**
  174.      * Connect to the LDAP server using the global options
  175.      *
  176.      * @access private
  177.      * @return object  Returns a PEAR error object if an error occurs.
  178.      */
  179.     function _connect()
  180.     {
  181.         // connect
  182.         if (isset($this->options['url']) && $this->options['url'] != '') {
  183.             $this->_debug('Connecting with URL', __LINE__);
  184.             $conn_params = array($this->options['url']);
  185.         } else {
  186.             $this->_debug('Connecting with host:port', __LINE__);
  187.             $conn_params = array($this->options['host'], $this->options['port']);
  188.         }
  189.  
  190.         if(($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) {
  191.             return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41, PEAR_ERROR_DIE);
  192.         }
  193.         $this->_debug('Successfully connected to server', __LINE__);
  194.  
  195.         // try switchig to LDAPv3
  196.         $ver = 0;
  197.         if(@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver >= 2) {
  198.             $this->_debug('Switching to LDAPv3', __LINE__);
  199.             @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, 3);
  200.         }
  201.  
  202.         // bind with credentials or anonymously
  203.         if($this->options['binddn'] && $this->options['bindpw']) {
  204.             $this->_debug('Binding with credentials', __LINE__);
  205.             $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
  206.         } else {
  207.             $this->_debug('Binding anonymously', __LINE__);
  208.             $bind_params = array($this->conn_id);
  209.         }
  210.         
  211.         // bind for searching
  212.         if ((@call_user_func_array('ldap_bind', $bind_params)) == false) {
  213.             $this->_debug();
  214.             $this->_disconnect();
  215.             return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41, PEAR_ERROR_DIE);
  216.         }
  217.         $this->_debug('Binding was successful', __LINE__);
  218.     }
  219.  
  220.     /**
  221.      * Disconnects (unbinds) from ldap server
  222.      *
  223.      * @access private
  224.      */
  225.     function _disconnect() 
  226.     {
  227.         if($this->_isValidLink()) {
  228.             $this->_debug('disconnecting from server');
  229.             @ldap_unbind($this->conn_id);
  230.         }
  231.     }
  232.  
  233.     /**
  234.      * Tries to find Basedn via namingContext Attribute
  235.      *
  236.      * @access private
  237.      */
  238.     function _getBaseDN()
  239.     {
  240.         if ($this->options['basedn'] == "" && $this->_isValidLink()) {           
  241.             $this->_debug("basedn not set, searching via namingContexts.", __LINE__);
  242.  
  243.             $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts"));
  244.             
  245.             if (ldap_count_entries($this->conn_id, $result_id) == 1) {
  246.                 
  247.                 $this->_debug("got result for namingContexts", __LINE__);
  248.                 
  249.                 $entry_id = ldap_first_entry($this->conn_id, $result_id);
  250.                 $attrs = ldap_get_attributes($this->conn_id, $entry_id);
  251.                 $basedn = $attrs['namingContexts'][0];
  252.  
  253.                 if ($basedn != "") {
  254.                     $this->_debug("result for namingContexts was $basedn", __LINE__);
  255.                     $this->options['basedn'] = $basedn;
  256.                 }
  257.             }
  258.             ldap_free_result($result_id);
  259.         }
  260.  
  261.         // if base ist still not set, raise error
  262.         if ($this->options['basedn'] == "") {
  263.             return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41, PEAR_ERROR_DIE);
  264.         }        
  265.         return true;
  266.     }
  267.  
  268.     /**
  269.      * determines whether there is a valid ldap conenction or not
  270.      *
  271.      * @accessd private
  272.      * @return boolean
  273.      */
  274.     function _isValidLink() 
  275.     {
  276.         if(is_resource($this->conn_id)) {
  277.             if(get_resource_type($this->conn_id) == 'ldap link') {
  278.                 return true;
  279.             }
  280.         }
  281.         return false;
  282.     }
  283.  
  284.     /**
  285.      * Set some default options
  286.      *
  287.      * @access private
  288.      */
  289.     function _setDefaults()
  290.     {
  291.         $this->options['host']        = 'localhost';
  292.         $this->options['port']        = '389';
  293.         $this->options['binddn']      = '';
  294.         $this->options['bindpw']      = '';
  295.         $this->options['scope']       = 'sub';
  296.         $this->options['basedn']      = '';
  297.         $this->options['userdn']      = '';
  298.         $this->options['userattr']    = "uid";
  299.         $this->options['useroc']      = 'posixAccount';
  300.         $this->options['groupdn']     = '';
  301.         $this->options['groupattr']   = 'cn';
  302.         $this->options['groupoc']     = 'groupOfUniqueNames';
  303.         $this->options['memberattr']  = 'uniqueMember';
  304.         $this->options['memberisdn']  = true;
  305.         $this->options['debug']       = false;
  306.     }
  307.  
  308.     /**
  309.      * Parse options passed to the container class
  310.      *
  311.      * @access private
  312.      * @param  array
  313.      */
  314.     function _parseOptions($array)
  315.     {
  316.         foreach ($array as $key => $value) {
  317.             $this->options[$key] = $value;
  318.         }
  319.  
  320.         // get the according search function for selected scope
  321.         switch($this->options['scope']) {
  322.         case 'one':
  323.             $this->ldap_search_func = 'ldap_list';
  324.             break;
  325.         case 'base':
  326.             $this->ldap_search_func = 'ldap_read';
  327.             break;
  328.         default:
  329.             $this->ldap_search_func = 'ldap_search';
  330.             break;
  331.         }
  332.         $this->_debug("LDAP search function will be: {$this->ldap_search_func}", __LINE__);
  333.     }
  334.  
  335.     /**
  336.      * Fetch data from LDAP server
  337.      *
  338.      * Searches the LDAP server for the given username/password
  339.      * combination.
  340.      *
  341.      * @param  string Username
  342.      * @param  string Password
  343.      * @return boolean
  344.      */
  345.     function fetchData($username, $password)
  346.     {        
  347.  
  348.         $this->_connect();
  349.         $this->_getBaseDN();
  350.         
  351.         // make search filter
  352.         $filter = sprintf('(&(objectClass=%s)(%s=%s))', $this->options['useroc'], $this->options['userattr'], $username);
  353.  
  354.         // make search base dn
  355.         $search_basedn = $this->options['userdn'];
  356.         if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
  357.             $search_basedn .= ',';
  358.         }
  359.         $search_basedn .= $this->options['basedn'];
  360.         
  361.         // make functions params array
  362.         $func_params = array($this->conn_id, $search_basedn, $filter, array($this->options['userattr']));
  363.  
  364.         $this->_debug("Searching with $filter in $search_basedn", __LINE__);
  365.  
  366.         // search
  367.         if (($result_id = @call_user_func_array($this->ldap_search_func, $func_params)) == false) {
  368.             $this->_debug('User not found', __LINE__);
  369.         } elseif (ldap_count_entries($this->conn_id, $result_id) == 1) { // did we get just one entry?
  370.  
  371.             $this->_debug('User was found', __LINE__);
  372.             
  373.             // then get the user dn
  374.             $entry_id = ldap_first_entry($this->conn_id, $result_id);
  375.             $user_dn  = ldap_get_dn($this->conn_id, $entry_id);
  376.  
  377.             ldap_free_result($result_id);
  378.  
  379.             // need to catch an empty password as openldap seems to return TRUE
  380.             // if anonymous binding is allowed
  381.             if ($password != "") {
  382.                 $this->_debug("Bind as $user_dn", __LINE__);                
  383.  
  384.                 // try binding as this user with the supplied password
  385.                 if (@ldap_bind($this->conn_id, $user_dn, $password)) {
  386.                     $this->_debug('Bind successful', __LINE__);
  387.  
  388.                     // check group if appropiate
  389.                     if(isset($this->options['group'])) {
  390.                         // decide whether memberattr value is a dn or the username
  391.                         $this->_debug('Checking group membership', __LINE__);
  392.                         return $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
  393.                     } else {
  394.                         $this->_debug('Authenticated', __LINE__);
  395.                         $this->_disconnect();
  396.                         return true; // user authenticated
  397.                     } // checkGroup
  398.                 } // bind
  399.             } // non-empty password
  400.         } // one entry
  401.         // default
  402.         $this->_debug('NOT authenticated!', __LINE__);
  403.         $this->_disconnect();
  404.         return false;
  405.     }
  406.  
  407.     /**
  408.      * Validate group membership
  409.      *
  410.      * Searches the LDAP server for group membership of the
  411.      * authenticated user
  412.      *
  413.      * @param  string Distinguished Name of the authenticated User
  414.      * @return boolean
  415.      */
  416.     function checkGroup($user) 
  417.     {
  418.         // make filter
  419.         $filter = sprintf('(&(%s=%s)(objectClass=%s)(%s=%s))',
  420.                           $this->options['groupattr'],
  421.                           $this->options['group'],
  422.                           $this->options['groupoc'],
  423.                           $this->options['memberattr'],
  424.                           $user
  425.                           );
  426.  
  427.         // make search base dn
  428.         $search_basedn = $this->options['groupdn'];
  429.         if($search_basedn != '' && substr($search_basedn, -1) != ',') {
  430.             $search_basedn .= ',';
  431.         }
  432.         $search_basedn .= $this->options['basedn'];
  433.         
  434.         $func_params = array($this->conn_id, $search_basedn, $filter, array($this->options['memberattr']));
  435.  
  436.         $this->_debug("Searching with $filter in $search_basedn", __LINE__);
  437.         
  438.         // search
  439.         if(($result_id = @call_user_func_array($this->ldap_search_func, $func_params)) != false) {
  440.             if(ldap_count_entries($this->conn_id, $result_id) == 1) {                
  441.                 ldap_free_result($result_id);
  442.                 $this->_debug('User is member of group', __LINE__);
  443.                 $this->_disconnect();
  444.                 return true;
  445.             }
  446.         }
  447.  
  448.         // default
  449.         $this->_debug('User is NOT member of group', __LINE__);
  450.         $this->_disconnect();
  451.         return false;
  452.     }
  453.  
  454.     /**
  455.      * Outputs debugging messages
  456.      *
  457.      * @access private
  458.      * @param string Debugging Message
  459.      * @param integer Line number
  460.      */
  461.     function _debug($msg = '', $line = 0)
  462.     {
  463.         if($this->options['debug'] === true) {
  464.             if($msg == '' && $this->_isValidLink()) {
  465.                 $msg = 'LDAP_Error: ' . @ldap_err2str(@ldap_errno($this->_conn_id));
  466.             }
  467.             print("$line: $msg <br />");
  468.         }
  469.     }
  470. }
  471.  
  472. ?>
  473.